﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
#if UNITY_EDITOR 
using UnityEditor;
#endif
using UnityEngine;

namespace Model
{
    /// <summary>
    /// AssetBundle信息
    /// </summary>
    public class ABInfo : IDisposable
    {
        private int refCount;
        public string Name { get; }

        public int RefCount
        {
            get
            {
                return this.refCount;
            }
            set
            {
                Debug.Log($"{this.Name} refcount:{value}");
                this.refCount = value;
            }
        }

        public AssetBundle AssetBundle { get; }

        public ABInfo(string name, AssetBundle ab)
        {
            this.Name = name;
            this.AssetBundle = ab;
            this.RefCount = 1;

        }

        public void Dispose()
        {
            Debug.Log($"unload ab {Name}");
            this.AssetBundle?.Unload(true);
        }
    }

    /// <summary>
    /// 资源管理器
    /// </summary>
    public class ResourcesManager : Singleton<ResourcesManager>, IDisposable
    {
        /// <summary>
        /// 主包的清单文件(依赖信息)
        /// </summary>
        public static AssetBundleManifest AssetBundleManifestObject { get; set; }

        /// <summary>
        /// 资源缓存
        /// </summary>
        private readonly Dictionary<string, UnityEngine.Object> resourcesCache = new Dictionary<string, UnityEngine.Object>();
        private readonly Dictionary<string, ABInfo> bundles = new Dictionary<string, ABInfo>();
        private readonly Dictionary<string, ABInfo> cacheDictionary = new Dictionary<string, ABInfo>();

        private ResourcesManager() { }

        /// <summary>
        /// 获取一个资源
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="bundleName"></param>
        /// <param name="prefab"></param>
        /// <returns></returns>
        public T GetAsset<T>(string bundleName, string prefab) where T : UnityEngine.Object
        {
            string path = $"{bundleName}/{prefab}".ToLower();

            UnityEngine.Object resource = null;
            if (!this.resourcesCache.TryGetValue(path, out resource))
            {
                throw new Exception("resource " + path + " not found !!! ");
            }
            return resource as T;
        }

        /// <summary>
        /// 卸载包
        /// </summary>
        /// <param name="assetBundleName"></param>
        public void UnloadBundle(string assetBundleName)
        {
            assetBundleName = assetBundleName.ToLower();
            this.UnloadOneBundle(assetBundleName);

            // 依赖
            string[] dependencies = ResourcesHelper.GetDependencies(assetBundleName);
            foreach (var item in dependencies)
            {
                this.UnloadOneBundle(item);
            }
        }

        /// <summary>
        /// 卸载一个包
        /// </summary>
        /// <param name="assetBundleName"></param>
        private void UnloadOneBundle(string assetBundleName)
        {
            assetBundleName = assetBundleName.ToLower();

            ABInfo aBInfo;
            if (!this.bundles.TryGetValue(assetBundleName, out aBInfo))
            {
                throw new Exception("not found bundle :" + assetBundleName);
            }

            --aBInfo.RefCount;
            if (aBInfo.RefCount > 0)
                return;

            this.bundles.Remove(assetBundleName);

            // 可以对包做缓存 ET框架是这样的....

            aBInfo.Dispose();
        }

        /// <summary>
        /// 同步加载assetBundleName
        /// </summary>
        /// <param name="assetBundleName"></param>
        public void LoadBundle(string assetBundleName)
        {
            // 转换为小写
            assetBundleName = assetBundleName.ToLower();

            // 先把要加载的包，加载进来。
            this.LoadOneBundle(assetBundleName);

            // 获取这个包的依赖
            string[] dependencies = ResourcesHelper.GetDependencies(assetBundleName);

            // 把所有依赖全部加载进来。
            foreach (string item in dependencies)
            {
                if (string.IsNullOrEmpty(item))
                {
                    continue;
                }
                this.LoadOneBundle(item);
            }
        }

        /// <summary>
        /// 加载一个AssetBundle包
        /// </summary>
        /// <param name="assetBundleName"></param>
        public void LoadOneBundle(string assetBundleName)
        {
            // 转换为小写
            assetBundleName = assetBundleName.ToLower();

            ABInfo abInfo;
            if (this.bundles.TryGetValue(assetBundleName, out abInfo)) // 如果已经加载过，就引用计数+1。
            {
                ++abInfo.RefCount;
                return;
            }

            // 没有加载过，试图从缓存中拿。
            if (this.cacheDictionary.ContainsKey(assetBundleName))
            {
                // 从缓存中拿，拿完后从缓存中移除。
                abInfo = this.cacheDictionary[assetBundleName];
                ++abInfo.RefCount;
                this.bundles[assetBundleName] = abInfo;
                this.cacheDictionary.Remove(assetBundleName);
                return;
            }
            if (!Defines.IsAsync)
            {
#if UNITY_EDITOR
                string[] realPath = AssetDatabase.GetAssetPathsFromAssetBundle(assetBundleName);

                foreach (var item in realPath)
                {
                    string asseteName = Path.GetFileNameWithoutExtension(item);
                    string path = $"{assetBundleName}/{asseteName}".ToLower();
                    UnityEngine.Object resource = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(item);
                    this.resourcesCache[path] = resource;
                }
                this.bundles[assetBundleName] = new ABInfo(assetBundleName, null);
                return;
#endif
            }
            // 同步加载AssetBundle
            AssetBundle assetBundle = AssetBundle.LoadFromFile(Path.Combine(PathHelper.AppHotfixResPath, assetBundleName));

            if (!assetBundle.isStreamedSceneAssetBundle)
            {
                UnityEngine.Object[] assets = assetBundle.LoadAllAssets();
                foreach (var asset in assets)
                {
                    string path = $"{assetBundleName}/{asset.name}".ToLower();
                    this.resourcesCache[path] = asset;
                }
            }

            this.bundles[assetBundleName] = new ABInfo(assetBundleName, assetBundle);
        }

        /// <summary>
        /// 释放相关资源
        /// </summary>
        public void Dispose()
        {
            foreach (var abInfo in this.bundles.Values)
            {
                abInfo.AssetBundle?.Unload(true);
            }

            this.bundles.Clear();
            this.cacheDictionary.Clear();
            this.resourcesCache.Clear();
        }
    }

}

